/* 
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - 

Title : com.Angaliwa.Users
Author : Dave Jansen -- Zenitum
URL : http://www.zenitum.com/

Description : The Users module allows you to easily
			  work with users in your project. Be it
			  for chat applications, online seminars
			  or just to show lists of who's online
			  and the like, this module allows you to
			  work with users with ease.

History		: v1.0.090629 Initial version

Created : June 29th, 2009

Version: 1.0
Compatibility: 1.0

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
*/

package com.Angaliwa
{
	import flash.events.SyncEvent;
	
	import mx.automation.codec.ArrayPropertyCodec;
	
	public class Users
	{
		/*
		 * Accepted daisyChain actions. Note that daisyChain is
		 * fully backwards-compatible. Any actions you specify that
		 * do not exist in earlier versions of this package will
		 * be silently ignored.
		 *
		 * Note that, as always, each accepted daisyChain action also
		 * comes with a before action.
		 *
		 * Version 1.0:
		 *  init
		 *  syncEvent
		 *  userJoined
		 *  userLeft
		 *  selfJoined
		 *  selfLeft
		 *
		 * Future version(s):
		 *  userStatusChanged
		 *  userSaidSomething
		 *  userSentPrivateMessage
		 *  userReceivedPrivateMessage
		 *  userAsksAttention
		 *  userStopsAskingForAttention
		 *  userGetsAttention
		 *  userNoLongerGetsAttention
		 *  userPrivelegeChanged
		 *  
		 */
		public var daisyChain:DaisyChain = new DaisyChain(this);
		public var soHandler:* = Core.g('so');
		public var userCollectionName:String = "users";
		public var self:String = Core.g('user.user.id'); 
		
		// The Users module is smart enough to notice which objects
		// cannot be stored on in SharedObjects, and places them
		// locally. This is handy, for example, for when you're
		// storing a NetStream session or the like with the user's
		// other information.
		private var local:Object = [];
		
		private var prevUsers:Array = new Array();
		private var firstRun:Boolean=true;
		
		public function init(soHandler:*=undefined, userCollectionName:String=undefined):Users{
			if(soHandler) this.soHandler = soHandler;
			if(userCollectionName) this.userCollectionName = userCollectionName;
			
			// Add a syncHandler to soHandler's daisyChain for user-
			// specific functions and checks.
			this.soHandler.daisyChain.a('syncHandler', syncHandler);
			
			return this;
		}
		
		private function syncHandler(e:SyncEvent):void{
			if(e.target.data[userCollectionName] is Array){
				// In case there already are users present, make sure
				// this client doesn't think all these users suddenly joined.
				if(firstRun) prevUsers = e.target.data[userCollectionName];
				
				// Keep an eye out for changes to the user list.
				e.changeList.some(function(item:*, index:int, array:Array):Boolean{
					if(item.name == userCollectionName){
						findUserChanges(e.target.data[userCollectionName], prevUsers);
						
						// To make sure prevUsers does not end up as a mere Alias
						// of the user array, we'll completley re-initialize it
						// and then inject all users.
						prevUsers = new Array();
						prevUsers = prevUsers.concat(e.target.data[userCollectionName]);
						
						return true;
					}
					else return false; 
				});
				
				daisyChain.run('syncHandler', e);
			}
			
			if(firstRun){
				daisyChain.run('init', prevUsers);
				firstRun=false;
			}
		}
		
		private function findUserChanges(array1:Array, array2:Array):void{
			var diff:Array = new Array();
			var found:Boolean;
			var i:int;
			
			// First we'll go through the first array, to see if anything
			// happened there.
			for(i=0; i < array1.length; i++){
				// Walk through the second array, in search for this user's ID.
				found = array2.some(function(item:*, index:int, array:Array):Boolean{
					if(item.id == array1[i].id) return true;
						else return false;
				});
				
				// When this user is not found in the second array, it 
				// usually means this user just joined this session.
				if(!found){
					if(array1[i].id == self)
						daisyChain.run('selfJoined', array1[i]);
					else 
						daisyChain.run('userJoined', array1[i]);
				}
			}
			
			// Now we'll compare the two arrays the other way around, so we
			// can detect if users left (or something else causing them to leave).
			for(i=0; i < array2.length; i++){
				// Walk through the first array, in search for this user's ID.
				found = array1.some(function(item:*, index:int, array:Array):Boolean{
					if(item.id == array2[i].id) return true;
						else return false;
				});
				
				// When this user is not found in first array, it
				// usually means this user just left the session.
				if(!found){
					if(array2[i].id == self)
						daisyChain.run('selfLeft', array2[i]);
					else
						daisyChain.run('userLeft', array2[i]);
				}
			}
		}
		
		/*
		 * Summary:      	Adds a new user to the current session.
		 * Return:       	Boolean
		 * Version:   	 	1.0
		 * Compatibility:	1.0
		 */
		public function a(name:String, id:String, optional:Object=undefined):Boolean{
			var users:Array, data:Object = [];
			
			// Check if there already are users present. If true,
			// add them to the newly created array. Also, we'll
			// run through them to see if we're not adding duplicate
			// users. Only the user's ID should be unique.
			if(this.soHandler.g(userCollectionName) is Array){
				users = this.soHandler.g(userCollectionName);
				
				// Walk through all users to make sure we're not
				// injecting a duplicate user.
				for(var i:int=0; i < users.length; i++){
					if(users[i].id == id)
						return false;
				}
			}
			
			// Add any optional information to the user object, before
			// we add id and name. This way, if id and name are set
			// through the optional fields, we'll gently overwrite them.
			if(optional){
				if(!local.hasOwnProperty(id))
					local[id] = [];
				
				for(var c:String in optional){
					var t:String = (typeof optional[c]);
					
					if(t == 'string' || t == 'int' || t == 'number'){
						trace('[Users] [A] Adding '+c+' ('+t+') ('+optional[c]+') to '+id);
						data[c] = optional[c];
					}else if( t == 'xml'){
						//if(optional[c].length() == 1){
							trace('[Users] [A] Sanitizing '+c+' ('+optional[c][0].toString()+')');
							data[c] = optional[c][0].toString();
						//}
						//else{
						//	trace('[Users] [A] Injecting '+c+' as an '+t+'.');
						//	data[c] = optional[c];
						//}
						
					}else{
						trace('[Users] [A] Adding '+c+' ('+optional[c]+') to local storage.');
						local[id][c] = optional[c];
					}
				}
			}
			
			
			// ID and Name are the only two values required.
			data.id = id;
			data.name = name;
			
			// Inject the user object into the users array.
			users.push(data);
			
			// Push the newly created array back to the Shared 
			// Object handler.
			this.soHandler.a(userCollectionName, users);
			
			// Return True to let the parent know the user was
			// successfully added.
			return true;
		}
		
		public function u(id:String, customSettings:Object):Users{
			if(this.soHandler.g(userCollectionName) is Array){
				var users:Array = this.soHandler.g(userCollectionName),
					item:int;
				
				// Walk through all users to make sure we're not
				// injecting a duplicate user.
				for(var i:int=0; i < users.length; i++){
					if(users[i].id == id)
						item = i;
				}
				
				// Add any optional information to the user object, before
				// we add id and name. This way, if id and name are set
				// through the optional fields, we'll gently overwrite them.
				if(item>-1){
					if(!local.hasOwnProperty(id))
						local[id] = [];
					
					for(var c:String in customSettings){
						var t:String = (typeof customSettings[c]);
						
						trace('TYPE: '+t+' ('+customSettings[c]+') ('+c+')');
						
						if(t == 'string' || t == 'int' || t == 'number'){
							trace('[Users] [U] Updating '+c+' ('+t+') ('+customSettings[c]+') to '+id);
							users[item][c] = customSettings[c];
						}else if( t == 'xml'){
							// || (t == 'object' && customSettings[c].hasOwnProperty('length'))
							//if(customSettings[c].hasOwnProperty('length') && customSettings[c].length == 1){
								trace('[Users] [U] Sanitizing '+c+' ('+customSettings[c][0].toString()+')');
								users[item][c] = (customSettings[c].hasOwnProperty('length')) ? customSettings[c][0].toString() : customSettings[c].toString();
							//}
							//else{
							//	trace('[Users] [U] Injecting '+c+' as an '+t+'.');
							//	users[item][c] = customSettings[c];
							//}
							
						}else{
							trace('[Users] [U] Updating '+c+' ('+customSettings[c]+') to local storage.');
							local[id][c] = customSettings[c];
						}
					}
					
					// Push the updated array back to the Shared 
					// Object handler.
					this.soHandler.a(userCollectionName, users);
				}
			}
			
			return this;
		}
		
		/*
		public function u(id:String, customSettings:Object):Users{
			if(!local.hasOwnProperty(id))
				local[id] = [];
			
			for(var c:String in customSettings){
				trace('[Users] [U] Updating '+c+' ('+customSettings[c]+') to '+id);
				local[id][c] = customSettings[c];
			}
			
			return this;
		}
		*/
		
		/*
		 * Summary:      	Gets and returns a specific user, based on 
		 					his/her ID.
		 * Return:       	Object
		 * Version:   	 	1.0
		 * Compatibility:	1.0
		 */
		public function g(id:String):Object{
			var users:Array = this.soHandler.g(userCollectionName);
			
			// Walk through the list with users to find the specified
			// user. Returns the user's object when found.
			if(users is Array && users.length > 0){
				for(var i:int=0; i < users.length; i++){
					if(users[i].id == id)
						return (local[users[i].id]) ? Toolbox.mergeObjects(users[i], local[users[i].id]) : users[i];
				}
			}
			
			// To keep the application from completely breaking when no
			// user is found, return an empty object.
			return [];
		}
		
		/*
		 * Summary:      	Removes (deletes) the specified user from the 
		 					current session, if present.
		 * Return:       	Boolean
		 * Version:   	 	1.0
		 * Compatibility:	1.0
		 */
		public function d(id:String):Boolean{
			var toReturn:Boolean = false;
			
			if(this.soHandler.g(userCollectionName) is Array){
				var users:Array = new Array();
				var oldUsers:Array = this.soHandler.g(userCollectionName);
				
				for(var i:int=0; i < oldUsers.length; i++){
					if(oldUsers[i].id == id) toReturn = true;
						else users.push(oldUsers[i]);
				}
				
				this.soHandler.a(userCollectionName, users);
			}
			
			return toReturn;
		}
		
		/*
		 * Summary:      	Returns an array with all currently present users.
		 * Return:       	Array
		 * Version:   	 	1.0
		 * Compatibility:	1.0
		 */
		 public function getAllUsers(sortDirection:ArrayPropertyCodec=undefined, sortField:String=undefined):Array{
		 	if(this.soHandler.g(userCollectionName) is Array){
		 		var allUsers:Array = this.soHandler.g(userCollectionName);
		 		
		 		if(sortField) return allUsers.sortOn(sortField,sortDirection);
		 			else return allUsers;
		 	}
		 	
		 	// In case no users are present, a blank Array will be sent
		 	// back so that chained functions expecting an Array will 
		 	// not break.
		 	return new Array();
		 }
	}
}